#include "globals.h"
#include "errno.h"

#include "util/string.h"
#include "util/debug.h"

#include "mm/mmobj.h"
#include "mm/pframe.h"
#include "mm/mm.h"
#include "mm/page.h"
#include "mm/slab.h"
#include "mm/tlb.h"

int anon_count = 0; /* for debugging/verification purposes */

static slab_allocator_t *anon_allocator;

static void anon_ref(mmobj_t *o);
static void anon_put(mmobj_t *o);
static int  anon_lookuppage(mmobj_t *o, uint32_t pagenum, int forwrite, pframe_t **pf);
static int  anon_fillpage(mmobj_t *o, pframe_t *pf);
static int  anon_dirtypage(mmobj_t *o, pframe_t *pf);
static int  anon_cleanpage(mmobj_t *o, pframe_t *pf);

static mmobj_ops_t anon_mmobj_ops = {
        .ref = anon_ref,
        .put = anon_put,
        .lookuppage = anon_lookuppage,
        .fillpage  = anon_fillpage,
        .dirtypage = anon_dirtypage,
        .cleanpage = anon_cleanpage
};

/*
 * This function is called at boot time to initialize the
 * anonymous page sub system. Currently it only initializes the
 * anon_allocator object.
 */
void
anon_init()
{
        anon_allocator = slab_allocator_create("anon", sizeof(mmobj_t));
	KASSERT(anon_allocator);
	dbg(DBG_TEST, "KASSERT PASS\n");
}

/*
 * You'll want to use the anon_allocator to allocate the mmobj to
 * return, then then initialize it. Take a look in mm/mmobj.h for
 * macros which can be of use here. Make sure your initial
 * reference count is correct.
 */
mmobj_t *
anon_create()
{
	mmobj_t *new_anon = NULL;

	new_anon = slab_obj_alloc(anon_allocator);
	if (new_anon) {
		mmobj_init(new_anon, &anon_mmobj_ops);
		new_anon->mmo_refcount++;
	}
	return new_anon;
}

/* Implementation of mmobj entry points: */

/*
 * Increment the reference count on the object.
 */
static void
anon_ref(mmobj_t *o)
{
	KASSERT(o && (0 < o->mmo_refcount) && (&anon_mmobj_ops == o->mmo_ops));
	dbg(DBG_TEST, "KASSERT PASS\n");
	o->mmo_refcount++;
}

/*
 * Decrement the reference count on the object. If, however, the
 * reference count on the object reaches the number of resident
 * pages of the object, we can conclude that the object is no
 * longer in use and, since it is an anonymous object, it will
 * never be used again. You should unpin and uncache all of the
 * object's pages and then free the object itself.
 */
static void
anon_put(mmobj_t *o)
{
	KASSERT(o && (0 < o->mmo_refcount) && (&anon_mmobj_ops == o->mmo_ops));
	dbg(DBG_TEST, "KASSERT PASS\n");

	pframe_t *temp_pframe = NULL;
	
	if (o->mmo_refcount == (o->mmo_nrespages + 1)) {
		while (!list_empty(&o->mmo_respages)) {
			temp_pframe = list_head(&o->mmo_respages, pframe_t, pf_olink);
			KASSERT(!pframe_is_busy(temp_pframe));
			pframe_unpin(temp_pframe);
			pframe_free(temp_pframe);
		}
	}
	o->mmo_refcount--;
	if (o->mmo_refcount == 0) {
		KASSERT(o->mmo_nrespages == 0);
		slab_obj_free(anon_allocator,o);
	}
}

/* Get the corresponding page from the mmobj. No special handling is
 * required. */
static int
anon_lookuppage(mmobj_t *o, uint32_t pagenum, int forwrite, pframe_t **pf)
{
	KASSERT(o);
	KASSERT(pf);
	
	pframe_t *temp_pframe = NULL;

	if (list_empty(&o->mmo_respages)) {
		return -EINVAL;
	}

	list_iterate_begin(&o->mmo_respages, temp_pframe, pframe_t, pf_olink) {
		if (temp_pframe->pf_pagenum == pagenum) {
			*pf = temp_pframe;
			return 0;
		}
	}list_iterate_end();

	return -EINVAL;
}

/* The following three functions should not be difficult. */

static int
anon_fillpage(mmobj_t *o, pframe_t *pf)
{
	/* this funcion is used when create a new pframe in anon */
	/* so just fill in all 0s */
	KASSERT(pframe_is_busy(pf));
	KASSERT(!pframe_is_pinned(pf));
	dbg(DBG_TEST, "KASSERT PASS\n");

	if (pf->pf_addr == memset(pf->pf_addr, 0, PAGE_SIZE)) {
		return 0;
	}
	else {
		return -EINVAL;
	}
}

static int
anon_dirtypage(mmobj_t *o, pframe_t *pf)
{
	/* we do not need to do any thing here */
	KASSERT(o);
	KASSERT(pf);

	return 0;
}

static int
anon_cleanpage(mmobj_t *o, pframe_t *pf)
{
	/* since this is a anon, if we want to clean it, we just delete is */
	/* so we need to do nothing here, pframe_cleanpage will do all necessary things */
	KASSERT(o);
	KASSERT(pf);

	return 0;
}
